/*********************************************************************************
 * Software License Agreement (BSD License)
 * Copyright (c) 2012, Carlos D. Correa (info@ngraph.org)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of netzen nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************************/
#include "glheaders.h"

#include "simpleGraphView.h"

#include <assert.h>
#include <string.h>
#include <math.h>
#include "renderUtils.h"
#include "mouseUtils.h"
#include "graph.h"
#include "layout.h"
#include "timing.h"
#include "controller.h"

#include <algorithm>

void SimpleGraphView::allocateNodeGeometry(int n) {
	if(n!=numVertices) {
		this->numVertices = n;
		if(colors) delete colors;
		if(vertices) delete vertices;
		if(silhouetteColors) delete silhouetteColors;
		colors = new float[4*n];
		silhouetteColors = new float[4*n];
		vertices = new float[2*n];
	}
}
void SimpleGraphView::allocateEdgeGeometry(int n) {
	if(n!=numIndices) {
		this->numIndices = n;
		if(indices) delete indices;
		indices = new int[2*n];
		
		if(edgeColors) delete edgeColors;
		edgeColors = new float[4*n];
		// Todo: curved segments
		//if(edgeVertices) delete edgeVertices;
		//edgeVertices = new float[2*n];
	}
}

void SimpleGraphView::getNodeLocation(int i, float &x, float &y) {
	if(layout) {
		layout->getLocation(i, x, y);
	} else {
		float t = (float) i/(float) graph->numNodes()*2.0*M_PI;
		x = cos(t);
		y = sin(t);
	}
}

void SimpleGraphView::setNodeLocation(int i, float x, float y) {
	if(layout) {
		layout->setLocation(i, x, y);
		layout->updated();
	}
	layoutUpdated();
}

void SimpleGraphView::createNodeGeometry() {
	NodeIterator *it = graph->createNodeIterator();
	for(it->begin();it->hasMoreElements();) {
		Node *node = (Node*) it->next();
		int i = node->getIndex();
		float x, y;
		getNodeLocation(i, x, y);
		vertices[2*i+0] = x;
		vertices[2*i+1] = y;
	}
	graph->destroyIterator(it);
}

void SimpleGraphView::createEdgeGeometry() {
	string thresholdFlag = getName() + ".Flag.AboveThresh";
	int numEdges = 0;
	string edgeFilterProperty = PROPERTY(getPropertyString("Edge.Filter"));
	string nodeFilterProperty = PROPERTY(getPropertyString("Node.Filter"));
	
	ElementIterator *it = graph->createEdgeIterator();
	for(it->begin();it->hasMoreElements();) {
		Edge *edge = (Edge*) it->next();
		bool b0 = graph->getAsBoolean(edge,edgeFilterProperty);
		bool filterPass1 = graph->getAsBoolean(edge->getNode0(),nodeFilterProperty);
		bool filterPass2 = graph->getAsBoolean(edge->getNode1(),nodeFilterProperty);
		if(b0 && filterPass1 && filterPass2) {
			edge->setPropertyBool(thresholdFlag, true);
			numEdges++;
		} else {
			edge->setPropertyBool(thresholdFlag, false);
		}
	}
	graph->destroyIterator(it);
	
	allocateEdgeGeometry(numEdges);
	int m = 0;
	it = graph->createEdgeIterator();
	for(it->begin();it->hasMoreElements();) {
		Edge *edge = (Edge*) it->next();
		int i = edge->getNode0()->getIndex();
		int j = edge->getNode1()->getIndex();
		bool filterPass1 = graph->getAsBoolean(edge->getNode0(),nodeFilterProperty);
		bool filterPass2 = graph->getAsBoolean(edge->getNode1(),nodeFilterProperty);
		bool aboveThresh = filterPass1 && filterPass2 && edge ->getPropertyBool(thresholdFlag);
		if(aboveThresh) {
			indices[2*m+0] = i;
			indices[2*m+1] = j;
			m++;
		}
	}
}

void SimpleGraphView::updateNodeColors() {
	if(!colors || !silhouetteColors) return;
	float rgb[3];
	string colormap = PROPERTY(getPropertyString("Node.Colormap"));
	float alpha0 = getPropertyFloat("Node.Opacity Factor");
	
	vector<PropertyBag*> selection;
	NodeIterator *it = graph->createNodeIterator();
	for(it->begin();it->hasMoreElements();) {
		Node *node = (Node*) it->next();
		int i = node->getIndex();
		rgb[0] = rgb[1] = rgb[2] = fColor_front;//0.0;
		getAsColor(node, PROPERTY(getPropertyString("Node.Color")), colormap, rgb);
		float x, y;
		getNodeLocation(i,x,y);
		
		if(dragMode==DRAG) {
			if(inSelection(x,y)) {
				selection.push_back(node);
			}
		}
		
		int selected = node->getPropertyInt("node.selected");
		if(selected>0) {
			getSelectionColor(selected, rgb);
		}
		float transparency = alpha0*graph->getAsFloat(node, PROPERTY(getPropertyString("Node.Opacity")),1.0);
		colors[4*i+0] = rgb[0];
		colors[4*i+1] = rgb[1];
		colors[4*i+2] = rgb[2];
		colors[4*i+3] = transparency;
		silhouetteColors[4*i+0] = 0;
		silhouetteColors[4*i+1] = 0;
		silhouetteColors[4*i+2] = 0;
		silhouetteColors[4*i+3] = transparency;
	}
	graph->destroyIterator(it);
	selectElements(selection, selectionMarker);
}

void SimpleGraphView::updateEdgeColors() {
	string thresholdFlag = getName() + ".Flag.AboveThresh";
	string cmap = PROPERTY(getPropertyString("Edge.Colormap"));
	string edgeProperty = PROPERTY(getPropertyString("Edge.Color"));
	float alpha0 = getPropertyFloat("Edge.Opacity Factor");
	int m = 0;
	EdgeIterator *it = graph->createEdgeIterator();
	for(it->begin();it->hasMoreElements();) {
		Edge * edge = (Edge*) it->next(); 
		int j = edge->getNode1()->getIndex();
		bool aboveThresh = edge->getPropertyBool(thresholdFlag);
		if(aboveThresh) {
			float rgb[3];
			rgb[0] = rgb[1] = rgb[2] = fColor_front;//0.0;
			getAsColor(edge, edgeProperty, cmap, rgb);
			float opacity = graph->getAsFloat(edge, PROPERTY(getPropertyString("Edge.Opacity")));
			float alpha = alpha0 * opacity;
			int selected = edge->getPropertyInt("node.selected");
			if(selected>0) {
				getSelectionColor(selected, rgb);
			}
			
			edgeColors[4*m+0] = rgb[0];
			edgeColors[4*m+1] = rgb[1];
			edgeColors[4*m+2] = rgb[2];
			edgeColors[4*m+3] = alpha;
			//printf("Adding indices %d %d --> %d\n", i, j, m);
			m++;
		}        
	}
	graph->destroyIterator(it);
}

void SimpleGraphView::initGraphics() {
}

void SimpleGraphView::drawNodeGeometry() {
	if(!vertices) return;
	if(!colors) return;
	float pointSize = getPropertyFloat("Node.Size Factor");
	float opacity = getPropertyFloat("Node.Opacity Factor");

	glEnableClientState(GL_VERTEX_ARRAY);	
	glEnableClientState(GL_COLOR_ARRAY);
	glVertexPointer(2, GL_FLOAT, 0, vertices);
	glColorPointer(4, GL_FLOAT, 0, silhouetteColors);
	glPointSize(pointSize+2.0);
	glDrawArrays(GL_POINTS, 0, numVertices);
	glPointSize(1);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);

	glMatrixMode(GL_COLOR);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	//glPopMatrix();
	
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	glVertexPointer(2, GL_FLOAT, 0, vertices);
	glColorPointer(4, GL_FLOAT, 0, colors);
	glPointSize(pointSize);
	glDrawArrays(GL_POINTS, 0, numVertices);
	glPointSize(1);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
}

void SimpleGraphView::drawEdgeGeometry() {
	if(!vertices) return;
	if(!indices) return;
	float opacity = getPropertyFloat("Edge.Opacity Factor");
	float lineWidth = getPropertyFloat("Edge.Size Factor");
	glLineWidth(lineWidth);

	if(PROPERTY(getPropertyString("Edge.Color"))=="Default" &&
		 PROPERTY(getPropertyString("Edge.Opacity"))=="Default") {

		glDisableClientState(GL_COLOR_ARRAY);
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(2, GL_FLOAT, 0, vertices);
		assert(indices);
		glColor4f(0,0,0,opacity);
		glDrawElements(GL_LINES, 2*numIndices, GL_UNSIGNED_INT, indices);
		glLineWidth(1);
		glDisableClientState(GL_VERTEX_ARRAY);
	} else {
		glBegin(GL_LINES);
		for(int m = 0; m < numIndices; m++) {
			glColor4fv((GLfloat*) &(edgeColors[4*m]));
			int i1 = indices[2*m+0];
			int i2 = indices[2*m+1];
			glVertex2fv(&(vertices[2*i1]));
			glVertex2fv(&(vertices[2*i2]));
			//glDrawElements(GL_LINES, 2, GL_UNSIGNED_INT, &(indices[2*m]));
		}
		glEnd();
	}
}

void SimpleGraphView::drawLabels() {
}

void SimpleGraphView::addLabel(Label *label, int importance) {
	// Feed into automatic labeling system. To be done.
}

/***
 PUBLIC
 ***/
	
void SimpleGraphView::invalidateGeometry() {
	validGeometry = false;
}
void SimpleGraphView::invalidateColors() {
	validColors = false;
}
void SimpleGraphView::invalidateIndices() {
	validIndices = false;
}
void SimpleGraphView::colorUpdated() {
	invalidateColors();
}
void SimpleGraphView::layoutUpdated() {
	invalidateGeometry();
}

SimpleGraphView::SimpleGraphView(const char *title):View("GraphView", title) {
	layout = 0;
	vertices = 0;
	colors = 0;
	silhouetteColors = 0;
	indices = 0;
	edgeColors = 0;
	edgeVertices = 0;
	numVertices = 0;
	numIndices = 0;
	graph = 0;
	
	selectedNode = -1;
	
	setPropertyFloat("Node.Size Factor", 1.0);
	setPropertyFloat("Node.Opacity Factor", 1.0);
	setPropertyFloat("Edge.Size Factor", 1.0);
	setPropertyFloat("Edge.Opacity Factor", 1.0);

	setPropertyStringList("Edge.Filter", "Default", "edge");
	setPropertyStringList("Node.Filter", "Default", "node");
	
	setPropertyStringList("Node.Color", Controller::addVirtualPrefix("node.color"), "node");
	setPropertyStringList("Node.Opacity", "Default", "node");
	setPropertyStringList("Node.Colormap", Controller::addVirtualPrefix("node.colormap"), "colormap");
	
	
	setPropertyStringList("Edge.Color", Controller::addVirtualPrefix("edge.color"), "edge");
	setPropertyStringList("Edge.Opacity", "Default", "edge");
	setPropertyStringList("Edge.Colormap", Controller::addVirtualPrefix("edge.colormap"), "colormap");

	string layoutFile = string(title)+".layout";
	setPropertyString("Graph.Layout File", layoutFile);
	setPropertyStringList("Graph.Layout", "None", "layout");
	
	validColors = false;
	validGeometry = false;
	validIndices = false;
	layoutChanged = true;
}

SimpleGraphView::~SimpleGraphView() {
	if(vertices) delete vertices;
	if(colors) delete colors;
	if(indices) delete indices;
	if(edgeColors) delete edgeColors;
	if(edgeVertices) delete edgeVertices;
}

void SimpleGraphView::render() {
	assert(graph);
	timestamp t1 = now();
	int N = (int) graph->numNodes();
	allocateNodeGeometry(N);
	
	
	initGraphics();
	
	string layoutName = getPropertyString("Graph.Layout");
	layout = controller->getLayout(layoutName);

	glPushMatrix();
	
	if(layoutChanged || !validGeometry) {
		createNodeGeometry();
	}
	if(!validIndices) {
		createEdgeGeometry();
	}
	if(!validColors) {
		updateNodeColors();
		updateEdgeColors();
	}
	

	drawEdgeGeometry();
	drawNodeGeometry();
	
	glPopMatrix();
	timestamp t2 = now();
	layoutChanged = false;
	validGeometry = true;
	validColors = true;
	validIndices = true;
}
void SimpleGraphView::setGraph(Graph *g) {
	View::setGraph(g);
}

// Inherited view methods
void SimpleGraphView::click(float x, float y, int modifiers) {
	View::click(x, y, modifiers);
	
	float rx, ry;
	toView(x,y,rx,ry);
	
	if(!pickNode(rx,ry)) {
		if(selectedNode>=0) {
			PropertyBag * node = graph->getNode(selectedNode);
			if(node) selectElement(node, 0);
		}
		selectedNode = -1;
	}
}

void SimpleGraphView::release(float px, float py, int modifiers) {
	View::release(px,py,modifiers);
}
void SimpleGraphView::drag(float px, float py, float dx, float dy, int modifiers) {
	float rdx, rdy;
	toViewDisplacement(dx,dy,rdx,rdy);
	if(selectedNode>=0) {
		float rx, ry;
		toView(px,py,rx,ry);
		setNodeLocation(selectedNode, rx, ry);
	} else {
		View::drag(px, py, dx, dy, modifiers);
		if(performingSelection) {
			controller->colorUpdated();
		}
	}
}
void SimpleGraphView::update(string property) {
	validGeometry = false;
	validColors = false;
	if(property=="View.Layout") {
		layoutChanged = true;
	}
	if(property=="Edge.Filter") {
		validIndices = false;
	}
}
void SimpleGraphView::tic() {
	ElementIterator *it = graph->createNodeIterator();
	for(it->begin();it->hasMoreElements();) {
		Node *node = (Node*) it->next();
		node->setVisible(true);
	}
	graph->destroyIterator(it);
}

bool SimpleGraphView::pickNode(float x, float y) {
	float nodeScale = getPropertyFloat("Node.Size Factor");
	
	ElementIterator *it = graph->createNodeIterator();
	for(it->begin();it->hasMoreElements();) {
		Node *node = (Node*) it->next();
		int i = node->getIndex();
		if(node->isVisible()) {
			float px,py;
			getNodeLocation(i,px,py);
			float d2 = (px-x)*(px-x)+(py-y)*(py-y);
			float rx = xpixels(nodeScale);
			float ry = ypixels(nodeScale);
			float r = rx*rx + ry*ry;
			if(d2<r) {
				selectedNode =	i;	
				selectElement(node, selectionMarker);
				graph->destroyIterator(it);
				printf("r = %f d2 = %f selected = %d\n", r, d2, selectedNode);
				return true;
			}
		}
	}
	graph->destroyIterator(it);
	return false;
}

View *createSimpleGraphView(string name) {
	SimpleGraphView* mainView = new SimpleGraphView(name.c_str());
	mainView->setLocation(0.0,0.0);
	mainView->setSize(1.0,1.0);
	mainView->setVisible(true);
	mainView->addCloseButton();
	mainView->addTransparentButton();
	mainView->addSelectionWidget();
	return mainView;
}


